﻿#region Header
// ------------------------ Licence / Copyright ------------------------
// 
// ScrumTable for Scrum Meeting Support
// Copyright © HSR - Hochschule für Technik Rapperswil
// All Rights Reserved
// 
// Author:
//  Michael Gfeller Silvan Gehrig Patrick Boos
// 
// ---------------------------------------------------------------------
#endregion

#region Usings

using System;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Windows;
using System.Windows.Data;
using Microsoft.Practices.Unity;
using ScrumTable.BL.DM.DataManagement;
using ScrumTable.Common;
using ScrumTable.Common.Collections;
using ScrumTable.Common.EventHandling;
using ScrumTable.Common.Logging;
using System.Linq;

#endregion

namespace ScrumTable.UI.ViewModel.Data
{
    /// <summary>
    /// Encapsulates the DomainCollection and retrieves underlying
    /// system events from the collection. So a DomainCollection
    /// can be used in the manner of WPF.
    /// </summary>
    /// <typeparam name="T">Type of the view model object.</typeparam>
    /// <typeparam name="TDomain">The type of the domain object.</typeparam>
    public class ObservableDomainCollection<T, TDomain> : ObservableCollection<T>
        where TDomain : DomainBase, new()
        where T : class, IViewModelData<TDomain>
    {
        #region Declarations
        //--------------------------------------------------------------------
        // Declarations
        //--------------------------------------------------------------------      
        private const string EvtKeyChanged = "OnKeyChanged";
        private readonly DelegateHandlerCollection<NotifyCollectionChangedEventHandler> _events = new DelegateHandlerCollection<NotifyCollectionChangedEventHandler>();
        private DomainCollection<TDomain> _collection;
        private bool _eventAdded;
        private bool _eventsEnabled = true;
        private Func<TDomain, T> _converter;
        private bool _innerEventsEnabled = true;
        private readonly Func<T, T, int> _comparer;
        private ScrumViewModelData _scrumData;
        private ICollectionView _collectionView;
        #endregion

        #region Constructors / Destructor
        //--------------------------------------------------------------------
        // Constructors / Destructor
        //--------------------------------------------------------------------

        /// <summary>
        /// Initializes a new instance of the <see cref="ObservableDomainCollection&lt;T, TDomain&gt;"/> class.
        /// </summary>
        public ObservableDomainCollection()
        {
        }
       
        public ObservableDomainCollection(ScrumViewModelData scrumData)
        {
            _scrumData = scrumData;
            if (scrumData != null)
            {
        
                _scrumData.PropertyChanged += new PropertyChangedEventHandler(OnScrumDataPropertyChanged);
                _collectionView = CollectionViewSource.GetDefaultView(this);
                _collectionView.Filter = null;
                _collectionView.Filter = Filter;
            }
        }



        /// <summary>
        /// Initializes a new instance of the <see cref="ObservableDomainCollection&lt;T, TDomain&gt;"/> class.
        /// </summary>
        /// <param name="comparer">The comparer function.</param>
        /// /// <param name="scrumData">scrumData if this property is set the collection gets filtered.</param>
        public ObservableDomainCollection(Func<T, T, int> comparer, ScrumViewModelData scrumData):this(scrumData)
        {
            _comparer = comparer;
        }

         /// <summary>
        /// Dispose instance manually.
        /// </summary>
        ~ObservableDomainCollection()
        {
            Dispose(false);
            
        }

        #endregion

        #region Methods
        //--------------------------------------------------------------------
        // Methods
        //--------------------------------------------------------------------

        /// <summary>
        /// Adds the specified item to the collection.
        /// </summary>
        /// <param name="itemToReposition">The item to add.</param>
        public void Reposition(T itemToReposition)
        {
            if(_comparer!=null)
            {
                int moveFromIndex = IndexOf(itemToReposition);
                bool alreadyAdded = false;
                for (int i = 0; i < Count; i++) // Todo: Optimize sort algo
                {
                    if(itemToReposition.Equals(this[i]))
                    {
                        break;
                    }

                    switch (Math.Sign(_comparer(this[i], itemToReposition)))
                    {
                        case 0:
                            break;
                        case 1:
                            alreadyAdded = true;
                            base.MoveItem(moveFromIndex, i);
                            break;
                        case -1:
                            break;
                    }
                    if(alreadyAdded)
                    {
                        break;
                    }
                }
                if(!alreadyAdded)
                {
                    base.MoveItem(moveFromIndex, Count);
                }
            }
        }

        /// <summary>
        /// Fills the data of the current list using the given collection
        /// and converter.
        /// </summary>
        /// <param name="collection">Specifies the collection of data to encapsulate.</param>
        /// <param name="converter">Specifies the convert function.</param>
        public void Fill(DomainCollection<TDomain> collection, Func<TDomain, T> converter)
        {
            PreCondition.AssertNotNull(collection, "collection");
            PreCondition.AssertNotNull(converter, "converter");

            ReInit();

            _converter = converter;
            _collection = collection;

            CallWithoutInnerEvents(delegate { Fill(); });
            EnsureEventAdded();
        }

        /// <summary>
        /// Implement dispose interface in order to support runtime support.
        /// </summary>
        public void Dispose()
        {
            Dispose(true);

            // Use SupressFinalize in case a subclass
            // of this type implements a finalizer.
            GC.SuppressFinalize(this);
        }

        /// <summary>
        /// Disposes the managed and unmanaged resources.
        /// </summary>
        /// <param name="disposing">True to dispose the managed resources, otherwise false.</param>
        protected virtual void Dispose(bool disposing)
        {
            //Release Unamanaged

            //Releasee Managed

            if( _scrumData != null)
            {
                _scrumData.PropertyChanged -= new PropertyChangedEventHandler(OnScrumDataPropertyChanged);
                _collectionView.Filter = null;
                _collectionView = null;
            }
            if (_collection != null && _collection.Context != null)
            {
                _collection.Context.CollectionChanged -= OnInnerCollectionChanged;
            }
        }

        
        /// <summary>
        /// Fills the data of the current list using the given collection
        /// and converter.
        /// </summary>
        private void Fill()
        {
            
            foreach (TDomain domain in _collection)
            {
                Add(_converter(domain));
            }
        }

        /// <summary>
        /// Removes all items from the collection.
        /// </summary>
        protected override void ClearItems()
        {
            //Logger<ObservableDomainCollection<T, TDomain>>.InfoFormat(
            //    "Call from {0}: @{1}->ClearItems( )",
            //    _innerEventsEnabled ? "UI" : "DM",
            //    _collection);

            base.ClearItems();
            ExecuteOnInnerCollection(() => _collection.Clear());
        }

        /// <summary>
        /// Inserts an item into the collection at the specified index.
        /// </summary>
        /// <param name="index">The zero-based index at which <paramref name="item"/> should be inserted.</param>
        /// <param name="item">The object to insert.</param>
        protected override void InsertItem(int index, T item)
        {
            //Logger<ObservableDomainCollection<T, TDomain>>.InfoFormat(
            //    "Call from {0}: @{1}->InsertItem( {2}, {3} )",
            //    _innerEventsEnabled ? "UI" : "DM",
            //    _collection,
            //    index,
            //    item.Original);

            if (_comparer != null)
            {
                bool alreadyAdded = false;

                for (int i = 0; i < Count; i++)
                {
                    switch (Math.Sign(_comparer(this[i], item)))
                    {
                        case 0:
                            break;
                        case 1:
                            alreadyAdded = true;
                            base.InsertItem(i, item);
                            break;
                        case -1:
                            break;
                    }
                    if (alreadyAdded)
                    {
                        break;
                    }
                }
                if (!alreadyAdded)
                {
                    base.InsertItem(Count, item);
                }
            }
            else
            {
                base.InsertItem(index, item);
            }
            ExecuteOnInnerCollection(() => _collection.Add(item.Original));
        }

        /// <summary>
        /// Replaces the element at the specified index.
        /// </summary>
        /// <param name="index">The zero-based index of the element to replace.</param>
        /// <param name="item">The new value for the element at the specified index.</param>
        protected override void SetItem(int index, T item)
        {
            //Logger<ObservableDomainCollection<T, TDomain>>.InfoFormat(
            //    "Call from {0}: @{1}->SetItem( {2}, {3} )",
            //    _innerEventsEnabled ? "UI" : "DM",
            //    _collection,
            //    index,
            //    item.Original);

            base.InsertItem(index, item);
            ExecuteOnInnerCollection(() => _collection.Add(item.Original));
        }

        /// <summary>
        /// Removes the item at the specified index of the collection.
        /// </summary>
        /// <param name="index">The zero-based index of the element to remove.</param>
        protected override void RemoveItem(int index)
        {
            T viewModelData = base[index];

            //Logger<ObservableDomainCollection<T, TDomain>>.InfoFormat(
            //    "Call from {0}: @{1}->RemoveItem( {2} )",
            //    _innerEventsEnabled ? "UI" : "DM",
            //    _collection,
            //    viewModelData.Original);

            base.RemoveItem(index);
            ExecuteOnInnerCollection(() => _collection.Remove(viewModelData.Original));
        }

        private bool Filter(object item)
        {
            return _scrumData.ContainsFilter(item);
        }


        private void EnsureEventAdded()
        {
            if (!_eventAdded && _collection != null)
            {
                _collection.CollectionChanged += OnInnerCollectionChanged;
                _eventAdded = true;
            }
        }

        private void EnsureEventRemoved()
        {
            if (_collection != null && _events.HasEvents(EvtKeyChanged))
            {
                _collection.CollectionChanged -= OnInnerCollectionChanged;
                _eventAdded = false;
            }
        }

        private void ReInit()
        {
            if (_collection != null)
            {
                _collection.CollectionChanged -= OnInnerCollectionChanged;
                _collection = null;
            }
            base.ClearItems();
            _eventAdded = false;
        }

        private void ExecuteOnInnerCollection(Action toExecute)
        {
            if (_innerEventsEnabled && _collection != null)
            {
                _eventsEnabled = false;

                try
                {
                    toExecute();
                }
                finally
                {
                    _eventsEnabled = true;
                }
            }
        }

        private void CallWithoutInnerEvents(Action toExecute)
        {
            try
            {
                _innerEventsEnabled = false;
                toExecute();
            }
            finally
            {
                _innerEventsEnabled = true;
            }
        }

        private void HandleInnerCollectionEvent(DomainCollectionChangedEventArgs e)
        {
            //if (e.Owner != _collection)
            //{
            //    return;
            //}

            CallWithoutInnerEvents(
                delegate
                {
                    if (e.Action == DomainCollectionChangedAction.Add)
                    {
                        Add(_converter(e.ChangedItem as TDomain));
                    }

                    if (e.Action == DomainCollectionChangedAction.Move ||
                        e.Action == DomainCollectionChangedAction.Remove)
                    {
                        Remove(_converter(e.ChangedItem as TDomain));
                    }
                });
        }
        
        #endregion

        #region Events
        //--------------------------------------------------------------------
        // Events
        //--------------------------------------------------------------------

        /// <summary>
        /// Occurs when an item is added, removed, changed, moved, or the entire list is refreshed.
        /// </summary>
        public override event NotifyCollectionChangedEventHandler CollectionChanged
        {
            add
            {
                _events.Add(EvtKeyChanged, value);
                EnsureEventAdded();
            }
            remove
            {
                _events.Remove(EvtKeyChanged, value);
                EnsureEventRemoved();
            }
        }

        /// <summary>
        /// Raises the <see cref="CollectionChanged"/> event.
        /// </summary>
        /// <param name="args">The <see cref="System.Collections.Specialized.NotifyCollectionChangedEventArgs"/> instance containing the event data.</param>
        protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs args)
        {
            if (_eventsEnabled)
            {
                using (BlockReentrancy())
                {
                    _events.Fire(EvtKeyChanged, this, args);
                }
            }
        }

        private void OnInnerCollectionChanged(object sender, DomainCollectionChangedEventArgs e)
        {
            if (!_eventsEnabled)
            {
                return;
            }

            //if (e is MultipleDomainCollectionChangedEventArgs)
            //{
            //    ((MultipleDomainCollectionChangedEventArgs)e).Children.ForEach(HandleInnerCollectionEvent);
            //}
            //else
            //{
                HandleInnerCollectionEvent(e);
            //}
        }

        private void OnScrumDataPropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            if (e.PropertyName == "Filter" && _scrumData != null)
            {
                _collectionView.Refresh();
            }
        }
        #endregion
    }


}